using System;
using System.Collections.Generic;
using System.Text;
using BizTalkCop.Rules.Bases;
using Microsoft.FxCop.Sdk;
using BizTalkCop.Rules.Enums;
using System.Xml.XPath;
using System.Xml;

namespace BizTalkCop.Rules.Naming.Rules
{
    /// <summary>
    /// Rule verifying orchestration shapes are correctly named
    /// </summary>
    /// <remarks>
    /// Checks shape names for: message constructs, compensation blocks and decision else branches
    /// </remarks>
    public class OrchestrationShapesShouldHaveCorrectName : OrchestrationRuleBase
    {
        /// <summary>
        /// Default constructor
        /// </summary>
        public OrchestrationShapesShouldHaveCorrectName() : base("OrchestrationShapesShouldHaveCorrectName") { }

        /// <summary>
        /// Checks if the orchestration's shapes are correctly named
        /// </summary>
        /// <param name="type">Type to check</param>
        /// <returns>Problem collection</returns>
        public override ProblemCollection Check(TypeNode type)
        {
            if (IsOrchestration(type))
            {
                CheckConstructs(type);
                CheckCompensations(type);
                CheckElseBranches(type);
                CheckTransforms(type);
            }
            return Problems;
        }

        private void CheckConstructs(TypeNode type)
        {
            //get the ConstructMessage shapes:
            XPathNodeIterator shapes = GetShapeInfos(type, ShapeType.ConstructShape);
            foreach (XPathNavigator shapeNavigator in shapes)
            {
                string shapeName = shapeNavigator.SelectSingleNode(XPathQuery.ShapeTextNode).Value;
                //expected format is Construct_<MessageName> - get the message name via OM:
                string shapeId = shapeNavigator.SelectSingleNode(XPathQuery.ShapeIdNode).Value;
                XPathNavigator omNavigator = GetOMElement(type, shapeId);
                XPathNavigator messageRef = SelectSingleNode(omNavigator, XPathQuery.OMConstructMessageRefProperyNode);
                if (messageRef != null)
                {
                    this.CheckName(shapeName, string.Format(this.GetConfiguredSetting(Setting.Construct),messageRef.Value), type);
                }
            }  
        }

        private void CheckTransforms(TypeNode type)
        {
            //get the Transform shapes:
            XPathNodeIterator shapes = GetShapeInfos(type, ShapeType.TransformShape);
            foreach (XPathNavigator shapeNavigator in shapes)
            {
                string shapeName = shapeNavigator.SelectSingleNode(XPathQuery.ShapeTextNode).Value;
                string shapeId = shapeNavigator.SelectSingleNode(XPathQuery.ShapeIdNode).Value;
                //get the id of the grandparent (Construct shape) node:
                string constructId = SelectSingleShapeNode(type, string.Format(XPathQuery.GrandparentIdByIdFormat, shapeId)).Value;
                //get the schema names of the In and Out messages:
                XPathNavigator metadataNavigator = shapeNavigator.SelectSingleNode(string.Format(XPathQuery.TrkMetadataFormat, constructId));
                string fromSchemaName = metadataNavigator.SelectSingleNode(XPathQuery.MessageInSchemaNode).Value;
                string toSchemaName = metadataNavigator.SelectSingleNode(XPathQuery.MessageOutSchemaNode).Value;
                //TODO  move to MemebrName
                string[] schemaParts = fromSchemaName.Split('.');
                fromSchemaName = schemaParts[schemaParts.Length - 1];
                schemaParts = toSchemaName.Split('.');
                toSchemaName = schemaParts[schemaParts.Length - 1];
                //expected format is Transform_<FromSchema>_<ToSchema>:
                this.CheckName(shapeName, string.Format(this.GetConfiguredSetting(Setting.Transform), fromSchemaName, toSchemaName), type);
            }
        }

        private void CheckCompensations(TypeNode type)
        {
            XPathNodeIterator shapes = GetShapeInfos(type, ShapeType.CompensationShape);
            string expectedName = this.GetConfiguredSetting(Setting.Compensate);
            foreach (XPathNavigator shapeNavigator in shapes)
            {
                string shapeName = shapeNavigator.SelectSingleNode(XPathQuery.ShapeTextNode).Value;                
                this.CheckName(shapeName, expectedName, type);
            }
        }

        private void CheckElseBranches(TypeNode type)
        {
            //get the Decide shapes:
            XPathNodeIterator shapes = GetShapeInfos(type, ShapeType.DecisionShape);
            foreach (XPathNavigator shapeNavigator in shapes)
            {
                string shapeName = shapeNavigator.SelectSingleNode(XPathQuery.ShapeTextNode).Value;
                bool elseBranch = false;
                //one of the children should be named "Else":
                string expectedName = this.GetConfiguredSetting(Setting.Else);
                foreach (XPathNavigator branchNavigator in shapeNavigator.Select(XPathQuery.DecisionBranchNameNode))
                {
                    if (branchNavigator.Value == expectedName)
                    {
                        elseBranch = true;
                        break;
                    }
                }
                if (!elseBranch)
                {
                    Problems.Add(new Problem(GetNamedResolution("Decision", shapeName, expectedName), type));
                }
            }
        }

        private void CheckName(string shapeName, string expectedName, TypeNode type)
        {
            if (shapeName != expectedName)
            {
                Problems.Add(new Problem(GetNamedResolution("Name", shapeName, expectedName), type));
            }
        }

                #region Configured settings

        static OrchestrationShapesShouldHaveCorrectName()
        {
            Dictionary<string, string> defaults = new Dictionary<string, string>();
            defaults.Add(Setting.Compensate, Default.Compensate);
            defaults.Add(Setting.Construct, Default.Construct);
            defaults.Add(Setting.Else, Default.Else);
            defaults.Add(Setting.Transform, Default.Transform);
            SetDefaults(typeof(OrchestrationShapesShouldHaveCorrectName), defaults);
        }

        private struct Setting
        {
            public const string Construct = "Construct";
            public const string Compensate = "Compensate";
            public const string Transform = "Transform";
            public const string Else = "Else";
        }

        private struct Default
        {
            public const string Construct = "Construct_{0}";
            public const string Compensate = "Compensate";
            public const string Transform = "Transform_{0}_{1}";
            public const string Else = "Else";
        }

        #endregion
    }
}
